package nowcoder

import (
	"fmt"
	"github.com/levigross/grequests"
	"github.com/skratchdot/open-golang/open"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"
)

const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"

func fetchStartTime(contestID int) (t time.Time, err error) {
	home := fmt.Sprintf("https://ac.nowcoder.com/acm/contest/%d", contestID)
	resp, err := grequests.Get(home, &grequests.RequestOptions{UserAgent: ua})
	if err != nil {
		return
	}
	if !resp.Ok {
		return time.Time{}, fmt.Errorf("%d", resp.StatusCode)
	}

	htmlStr := resp.String()
	const token = `"startTime":`
	i := strings.Index(htmlStr, token)
	if i == -1 {
		return time.Time{}, fmt.Errorf("invalid html content %s", htmlStr)
	}

	tsStr := htmlStr[i+len(token) : i+len(token)+10]
	ts, err := strconv.Atoi(tsStr)
	if err != nil {
		return
	}
	return time.Unix(int64(ts), 0), nil
}

func login(emailOrPhone, cipherPwd string) (session *grequests.Session, err error) {
	session = grequests.NewSession(&grequests.RequestOptions{
		UserAgent:    ua,
		UseCookieJar: true,
	})

	resp, err := session.Post("https://www.nowcoder.com/nccommon/login/do", &grequests.RequestOptions{
		Data: map[string]string{
			"email":     emailOrPhone,
			"cipherPwd": cipherPwd,
		},
	})
	if err != nil {
		return
	}
	if !resp.Ok {
		return nil, fmt.Errorf("%d", resp.StatusCode)
	}
	return
}

// 若 problemNum 为 0 则比赛尚未开始
func fetchProblems(session *grequests.Session, contestID int) (problemNum int, err error) {
	api := fmt.Sprintf("https://ac.nowcoder.com/acm/contest/problem-list?id=%d", contestID)
	resp, err := session.Get(api, nil)
	if err != nil {
		return
	}
	if !resp.Ok {
		return 0, fmt.Errorf("%d", resp.StatusCode)
	}

	d := struct {
		Code int `json:"code"`
		Data struct {
			BasicInfo struct {
				ProblemCount int `json:"problemCount"`
			} `json:"basicInfo"`
		} `json:"data"`
	}{}
	if err = resp.JSON(&d); err != nil || d.Code != 0 {
		return
	}

	problemNum = d.Data.BasicInfo.ProblemCount
	return
}

// examples 为输入输出交替
func parseCodeAndExamples(session *grequests.Session, problemURL string) (code string, examples []string, err error) {
	resp, err := session.Get(problemURL, nil)
	if err != nil {
		return
	}
	if !resp.Ok {
		return "", nil, fmt.Errorf("%d", resp.StatusCode)
	}

	root, err := html.Parse(resp)
	if err != nil {
		return
	}

	var f func(o *html.Node)
	f = func(o *html.Node) {
		if o.DataAtom == atom.Textarea {
			for _, attribute := range o.Attr {
				if attribute.Val == "input" || attribute.Val == "output" {
					examples = append(examples, strings.TrimSpace(o.FirstChild.Data))
					break
				}
				if attribute.Val == "goTpl" {
					code = o.FirstChild.Data
					break
				}
			}
		}
		for c := o.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}
	}
	f(root)
	return
}

func genMainFileContent(code, funcComment string) string {
	code = strings.TrimSpace(code)
	imports := ""
	if hasPredefinedType := strings.Contains(code, `"nc_tools"`); hasPredefinedType {
		imports = `
//import . "nc_tools"
import . "github.com/EndlessCheng/codeforces-go/leetcode/testutil"
`
	}

	if funcComment != "" {
		funcComment += "\n"
	}

	i := strings.Index(code, "func ")
	if i == -1 {
		return ""
	}
	code = code[i:]
	code = strings.Replace(code, "// write code here", "\n\treturn", -1)

	content := fmt.Sprintf(`package main
%s
%s%s
`, imports, funcComment, code)
	return content
}

func genTestFileContent(id byte, rawExamples []string, funcName, problemURL string) string {
	exampleType := "[][]string"
	testUtilFunc := "testutil.RunLeetCodeFuncWithExamples"
	examples := ""
	for i := 0; i < len(rawExamples); i += 2 {
		examples += "\n\t\t{\n\t\t\t"
		sp := splitRawInput(rawExamples[i])
		for _, s := range sp {
			examples += "`" + s + "`,"
		}
		examples += "\n\t\t\t"
		examples += "`" + rawExamples[i+1] + "`,"
		examples += "\n\t\t},"
	}
	content := fmt.Sprintf(`// Code generated by copypasta/template/nowcoder/generator_test.go
package main

import (
	"github.com/EndlessCheng/codeforces-go/leetcode/testutil"
	"testing"
)

func Test(t *testing.T) {
	t.Log("Current test is [%c]")
	examples := %s{%s
		// TODO 测试参数的下界和上界
		
	}
	targetCaseNum := 0
	if err := %s(t, %s, examples, targetCaseNum); err != nil {
		t.Fatal(err)
	}
}
// %s
`, id, exampleType, examples, testUtilFunc, funcName, problemURL)
	return content
}

func GenNowCoderTemplates(emailOrPhone, cipherPwd, contestDir string, contestID int, funcComment string) error {
	startTime, err := fetchStartTime(contestID)
	if err != nil {
		return err
	}

	if t := time.Until(startTime); t > 0 {
		t += 5 * time.Second
		fmt.Println("sleep", t)
		time.Sleep(t)
	}

	// 必须要在开赛后登录才能进行后续操作
	var session *grequests.Session
	var problemNum int
	for {
		session, err = login(emailOrPhone, cipherPwd)
		if err != nil {
			return err
		}
		fmt.Println("登录成功")
		problemNum, _ = fetchProblems(session, contestID)
		if problemNum > 0 {
			break
		}
		// 正常不会走到这里
		const sec = 5
		fmt.Println("sleep", sec)
		time.Sleep(time.Duration(sec) * time.Second)
	}
	fmt.Println("本次比赛有", problemNum, "题")

o:
	for id := byte('a'); id < 'a'+byte(problemNum); id++ {
		problemURL := fmt.Sprintf("https://ac.nowcoder.com/acm/contest/%d/%c", contestID, id)
		var code string
		var examples []string
		for {
			code, examples, err = parseCodeAndExamples(session, problemURL)
			if err != nil {
				fmt.Println("[error] parseCodeAndExamples", problemURL, err)
				continue o
			}
			if len(examples) > 0 {
				break
			}
			fmt.Println("样例为空，重试...")
			time.Sleep(time.Second)
		}

		mainStr := genMainFileContent(code, funcComment)
		i := strings.Index(code, "func ") + 5
		j := strings.IndexByte(code[i:], '(') + i
		funcName := code[i:j]
		testStr := genTestFileContent(id, examples, funcName, problemURL)

		problemDir := contestDir + string(id) + "/"
		if err = os.MkdirAll(problemDir, os.ModePerm); err != nil {
			return err
		}
		mainFilePath := problemDir + fmt.Sprintf("%c.go", id)
		if err = ioutil.WriteFile(mainFilePath, []byte(mainStr), 0644); err != nil {
			return err
		}
		if err = ioutil.WriteFile(problemDir+fmt.Sprintf("%c_test.go", id), []byte(testStr), 0644); err != nil {
			return err
		}

		if id == 'a' {
			open.Run(absPath(mainFilePath))
		}
	}

	fmt.Println("Done.")
	return nil
}
